/*
 * The contents of this file are subject to the Mozilla Public
 * License Version 1.1 (the "License"); you may not use this file
 * except in compliance with the License. You may obtain a copy of
 * the License at http://www.mozilla.org/MPL/
 *
 * Software distributed under the License is distributed on an "AS
 * IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
 * implied. See the License for the specific language governing
 * rights and limitations under the License.
 *
 * The Original Code is MPEG4IP.
 *
 * The Initial Developer of the Original Code is Cisco Systems Inc.
 * Portions created by Cisco Systems Inc. are
 * Copyright (C) Cisco Systems Inc. 2001 - 2004.  All Rights Reserved.
 *
 * 3GPP features implementation is based on 3GPP's TS26.234-v5.60,
 * and was contributed by Ximpo Group Ltd.
 *
 * Portions created by Ximpo Group Ltd. are
 * Copyright (C) Ximpo Group Ltd. 2003, 2004.  All Rights Reserved.
 *
 * Contributor(s):
 *      Dave Mackie       dmackie@cisco.com
 *              Alix Marchandise-Franquet alix@cisco.com
 *              Ximpo Group Ltd.                mp4v2@ximpo.com
 */

#include "src/impl.h"

namespace mp4v2
{
    namespace impl
    {

        ///////////////////////////////////////////////////////////////////////////////

        static const uint8_t BifsV2Config[3] =
        {
            0x00, 0x00, 0x60 // IsCommandStream = 1, PixelMetric = 1
        };

        void MP4File::MakeIsmaCompliant(bool addIsmaComplianceSdp)
        {
            ProtectWriteOperation(__FILE__, __LINE__, __FUNCTION__);

            if (m_useIsma)
            {
                // already done
                return;
            }

            // find first audio and/or video tracks

            MP4TrackId audioTrackId = MP4_INVALID_TRACK_ID;
            try
            {
                audioTrackId = FindTrackId(0, MP4_AUDIO_TRACK_TYPE);
            }
            catch ( Exception *x )
            {
                log.errorf(*x);
                delete x;
            }
            MP4TrackId videoTrackId = MP4_INVALID_TRACK_ID;
            try
            {
                videoTrackId = FindTrackId(0, MP4_VIDEO_TRACK_TYPE);
            }
            catch (Exception *x)
            {
                log.errorf(*x);
                delete x;
            }
            if (audioTrackId == MP4_INVALID_TRACK_ID &&
                    videoTrackId == MP4_INVALID_TRACK_ID) return;

            const char *audio_media_data_name, *video_media_data_name;
            uint8_t videoProfile = 0xff;
            if (audioTrackId != MP4_INVALID_TRACK_ID)
            {
                audio_media_data_name = MP4GetTrackMediaDataName(this, audioTrackId);
                if (!(ATOMID(audio_media_data_name) == ATOMID("mp4a") ||
                        ATOMID(audio_media_data_name) == ATOMID("enca")))
                {
                    log.errorf("%s: \"%s\": can't make ISMA compliant when file contains an %s track",
                               __FUNCTION__, GetFilename().c_str(), audio_media_data_name);
                    return;
                }
            }
            //
            // Note - might have to check for avc1 here...
            if (videoTrackId != MP4_INVALID_TRACK_ID)
            {
                video_media_data_name = MP4GetTrackMediaDataName(this, videoTrackId);
                if (!(ATOMID(video_media_data_name) == ATOMID("mp4v") ||
                        ATOMID(video_media_data_name) == ATOMID("encv")))
                {
                    log.errorf("%s: \"%s\": can't make ISMA compliant when file contains an %s track", __FUNCTION__,
                               GetFilename().c_str(), video_media_data_name);
                    return;
                }
                MP4LogLevel verb = log.verbosity;
                log.setVerbosity(MP4_LOG_NONE);
                videoProfile = MP4GetVideoProfileLevel(this, videoTrackId);
                log.setVerbosity(verb);
            }

            m_useIsma = true;

            uint64_t fileMsDuration = 0;
            fileMsDuration =
                ConvertFromMovieDuration(GetDuration(), MP4_MSECS_TIME_SCALE);

            // delete any existing OD track
            if (m_odTrackId != MP4_INVALID_TRACK_ID)
            {
                DeleteTrack(m_odTrackId);
            }

            if (m_pRootAtom->FindAtom("moov.iods") == NULL)
            {
                (void)AddChildAtom("moov", "iods");
            }
            (void)AddODTrack();
            SetODProfileLevel(0xFF);

            if (audioTrackId != MP4_INVALID_TRACK_ID)
            {
                AddTrackToOd(audioTrackId);
                MP4SetAudioProfileLevel(this, 0xf);
            }
            if (videoTrackId != MP4_INVALID_TRACK_ID)
            {
                AddTrackToOd(videoTrackId);
                MP4SetVideoProfileLevel(this, videoProfile);
            }

            // delete any existing scene track
            MP4TrackId sceneTrackId = MP4_INVALID_TRACK_ID;
            try
            {
                sceneTrackId = FindTrackId(0, MP4_SCENE_TRACK_TYPE);
            }
            catch (Exception *x)
            {
                log.errorf(*x);
                delete x;
            }
            if (sceneTrackId != MP4_INVALID_TRACK_ID)
            {
                DeleteTrack(sceneTrackId);
            }

            // add scene track
            sceneTrackId = AddSceneTrack();
            SetSceneProfileLevel(0xFF);
            SetGraphicsProfileLevel(0xFF);
            SetTrackIntegerProperty(sceneTrackId,
                                    "mdia.minf.stbl.stsd.mp4s.esds.decConfigDescr.objectTypeId",
                                    MP4SystemsV2ObjectType);

            SetTrackESConfiguration(sceneTrackId,
                                    BifsV2Config, sizeof(BifsV2Config));

            uint8_t *pBytes = NULL;
            uint64_t numBytes = 0;

            // write OD Update Command
            CreateIsmaODUpdateCommandFromFileForFile(
                m_odTrackId,
                audioTrackId,
                videoTrackId,
                &pBytes,
                &numBytes);

            WriteSample(m_odTrackId, pBytes, numBytes, fileMsDuration);

            MP4Free(pBytes);
            pBytes = NULL;

            // write BIFS Scene Replace Command
            CreateIsmaSceneCommand(
                MP4_IS_VALID_TRACK_ID(audioTrackId),
                MP4_IS_VALID_TRACK_ID(videoTrackId),
                &pBytes,
                &numBytes);

            WriteSample(sceneTrackId, pBytes, numBytes, fileMsDuration);

            MP4Free(pBytes);
            pBytes = NULL;

            // add session level sdp
            CreateIsmaIodFromFile(
                m_odTrackId,
                sceneTrackId,
                audioTrackId,
                videoTrackId,
                &pBytes,
                &numBytes);

            char *iodBase64 = MP4ToBase64(pBytes, numBytes);

            uint32_t sdpBufLen = (uint32_t)strlen(iodBase64) + 256;
            uint32_t used;
            char *sdpBuf = (char *)MP4Calloc(sdpBufLen);

            if (addIsmaComplianceSdp)
            {
                strncpy(sdpBuf, "a=isma-compliance:1,1.0,1\015\012", sdpBufLen);
            }

            used = (uint32_t)strlen(sdpBuf);
            sdpBufLen -= used;
            snprintf(&sdpBuf[used], sdpBufLen,
                     "a=mpeg4-iod: \042data:application/mpeg4-iod;base64,%s\042\015\012",
                     iodBase64);

            SetSessionSdp(sdpBuf);

            log.verbose1f("\"%s\": IOD SDP = %s", GetFilename().c_str(), sdpBuf);

            MP4Free(iodBase64);
            iodBase64 = NULL;
            MP4Free(pBytes);
            pBytes = NULL;
            MP4Free(sdpBuf);
            sdpBuf = NULL;
        }

        static void CloneIntegerProperty(
            MP4Descriptor *pDest,
            MP4DescriptorProperty *pSrc,
            const char *name)
        {
            MP4IntegerProperty *pGetProperty;
            MP4IntegerProperty *pSetProperty;

            if (!pSrc->FindProperty(name, (MP4Property **)&pGetProperty)) return;
            if (!pDest->FindProperty(name, (MP4Property **)&pSetProperty)) return;
            pSetProperty->SetValue(pGetProperty->GetValue());
        }

        void MP4File::CreateIsmaIodFromFile(
            MP4TrackId odTrackId,
            MP4TrackId sceneTrackId,
            MP4TrackId audioTrackId,
            MP4TrackId videoTrackId,
            uint8_t **ppBytes,
            uint64_t *pNumBytes)
        {
            MP4Atom *pIodsAtom = FindAtom("moov.iods");
            ASSERT(pIodsAtom);
            MP4DescriptorProperty *pSrcIod =
                (MP4DescriptorProperty *)pIodsAtom->GetProperty(2);

            MP4Descriptor *pIod = new MP4IODescriptor(*pIodsAtom);
            pIod->SetTag(MP4IODescrTag);
            pIod->Generate();

            CloneIntegerProperty(pIod, pSrcIod, "objectDescriptorId");
            CloneIntegerProperty(pIod, pSrcIod, "ODProfileLevelId");
            CloneIntegerProperty(pIod, pSrcIod, "sceneProfileLevelId");
            CloneIntegerProperty(pIod, pSrcIod, "audioProfileLevelId");
            CloneIntegerProperty(pIod, pSrcIod, "visualProfileLevelId");
            CloneIntegerProperty(pIod, pSrcIod, "graphicsProfileLevelId");

            // mutate esIds from MP4ESIDIncDescrTag to MP4ESDescrTag
            MP4DescriptorProperty *pEsProperty;
            if (!pIod->FindProperty("esIds", (MP4Property **)&pEsProperty)) return;
            pEsProperty->SetTags(MP4ESDescrTag);

            MP4IntegerProperty *pSetProperty;
            MP4IntegerProperty *pSceneESID;
            MP4IntegerProperty *pOdESID;

            // OD
            MP4Descriptor *pOdEsd =
                pEsProperty->AddDescriptor(MP4ESDescrTag);
            pOdEsd->Generate();

            if (!pOdEsd->FindProperty("ESID", (MP4Property **)&pOdESID)) return;

            // we set the OD ESID to a non-zero unique value
            pOdESID->SetValue(m_odTrackId);

            if (pOdEsd->FindProperty("URLFlag",
                                     (MP4Property **)&pSetProperty))
                pSetProperty->SetValue(1);

            uint8_t *pBytes;
            uint64_t numBytes;

            CreateIsmaODUpdateCommandFromFileForStream(
                audioTrackId,
                videoTrackId,
                &pBytes,
                &numBytes);

            log.hexDump(0, MP4_LOG_VERBOSE1, pBytes, numBytes, "\"%s\": OD data",
                        GetFilename().c_str() );

            char *odCmdBase64 = MP4ToBase64(pBytes, numBytes);

            uint32_t urlBufLen = (uint32_t)strlen(odCmdBase64) + 64;
            char *urlBuf = (char *)MP4Malloc(urlBufLen);

            snprintf(urlBuf, urlBufLen,
                     "data:application/mpeg4-od-au;base64,%s",
                     odCmdBase64);

            MP4StringProperty *pUrlProperty;
            if (pOdEsd->FindProperty("URL",
                                     (MP4Property **)&pUrlProperty))
                pUrlProperty->SetValue(urlBuf);

            log.verbose1f("\"%s\": OD data URL = \042%s\042", GetFilename().c_str(),
                          urlBuf);

            MP4Free(odCmdBase64);
            odCmdBase64 = NULL;
            MP4Free(pBytes);
            pBytes = NULL;
            MP4Free(urlBuf);
            urlBuf = NULL;

            MP4DescriptorProperty *pSrcDcd = NULL;

            // HACK temporarily point to scene decoder config
            (void)FindProperty(MakeTrackName(odTrackId,
                                             "mdia.minf.stbl.stsd.mp4s.esds.decConfigDescr"),
                               (MP4Property **)&pSrcDcd);
            ASSERT(pSrcDcd);
            MP4Property *pOrgOdEsdProperty =
                pOdEsd->GetProperty(8);
            pOdEsd->SetProperty(8, pSrcDcd);

            // bufferSizeDB needs to be set appropriately
            MP4BitfieldProperty *pBufferSizeProperty = NULL;
            if (pOdEsd->FindProperty("decConfigDescr.bufferSizeDB",
                                     (MP4Property **)&pBufferSizeProperty))
            {
                ASSERT(pBufferSizeProperty);
                pBufferSizeProperty->SetValue(numBytes);
            }

            // SL config needs to change from 2 (file) to 1 (null)
            if (pOdEsd->FindProperty("slConfigDescr.predefined",
                                     (MP4Property **)&pSetProperty))
                pSetProperty->SetValue(1);


            // Scene
            MP4Descriptor *pSceneEsd =
                pEsProperty->AddDescriptor(MP4ESDescrTag);
            pSceneEsd->Generate();

            if (pSceneEsd->FindProperty("ESID",
                                        (MP4Property **)&pSceneESID))
            {
                // we set the Scene ESID to a non-zero unique value
                pSceneESID->SetValue(sceneTrackId);
            }

            if (pSceneEsd->FindProperty("URLFlag",
                                        (MP4Property **)&pSetProperty))
                pSetProperty->SetValue(1);

            CreateIsmaSceneCommand(
                MP4_IS_VALID_TRACK_ID(audioTrackId),
                MP4_IS_VALID_TRACK_ID(videoTrackId),
                &pBytes,
                &numBytes);

            log.hexDump(0, MP4_LOG_VERBOSE1, pBytes, numBytes, "\"%s\": Scene data",
                        GetFilename().c_str() );

            char *sceneCmdBase64 = MP4ToBase64(pBytes, numBytes);

            urlBuf = (char *)MP4Malloc(strlen(sceneCmdBase64) + 64);
            snprintf(urlBuf, strlen(sceneCmdBase64) + 64,
                     "data:application/mpeg4-bifs-au;base64,%s",
                     sceneCmdBase64);

            if (pSceneEsd->FindProperty("URL",
                                        (MP4Property **)&pUrlProperty))
                pUrlProperty->SetValue(urlBuf);

            log.verbose1f("\"%s\": Scene data URL = \042%s\042",
                          GetFilename().c_str(), urlBuf);

            MP4Free(sceneCmdBase64);
            sceneCmdBase64 = NULL;
            MP4Free(urlBuf);
            urlBuf = NULL;
            MP4Free(pBytes);
            pBytes = NULL;

            // HACK temporarily point to scene decoder config
            ASSERT(FindProperty(MakeTrackName(sceneTrackId,
                                              "mdia.minf.stbl.stsd.mp4s.esds.decConfigDescr"),
                                (MP4Property **)&pSrcDcd));
            ASSERT(pSrcDcd);
            MP4Property *pOrgSceneEsdProperty =
                pSceneEsd->GetProperty(8);
            pSceneEsd->SetProperty(8, pSrcDcd);

            // bufferSizeDB needs to be set
            pBufferSizeProperty = NULL;
            if (pSceneEsd->FindProperty("decConfigDescr.bufferSizeDB",
                                        (MP4Property **)&pBufferSizeProperty))
            {
                ASSERT(pBufferSizeProperty);
                pBufferSizeProperty->SetValue(numBytes);
            }

            // SL config needs to change from 2 (file) to 1 (null)
            if (pSceneEsd->FindProperty("slConfigDescr.predefined",
                                        (MP4Property **)&pSetProperty))
                pSetProperty->SetValue(1);


            // finally get the whole thing written to a memory
            pIod->WriteToMemory(*this, ppBytes, pNumBytes);


            // now carefully replace esd properties before destroying
            pOdEsd->SetProperty(8, pOrgOdEsdProperty);
            pSceneEsd->SetProperty(8, pOrgSceneEsdProperty);
            pSceneESID->SetValue(0); // restore 0 value
            pOdESID->SetValue(0);

            delete pIod;

            log.hexDump(0, MP4_LOG_VERBOSE1, *ppBytes, *pNumBytes, "\"%s\": IOD data",
                        GetFilename().c_str() );
        }

        void MP4File::CreateIsmaIodFromParams(
            uint8_t videoProfile,
            uint32_t videoBitrate,
            uint8_t *videoConfig,
            uint32_t videoConfigLength,
            uint8_t audioProfile,
            uint32_t audioBitrate,
            uint8_t *audioConfig,
            uint32_t audioConfigLength,
            uint8_t **ppIodBytes,
            uint64_t *pIodNumBytes)
        {
            MP4IntegerProperty *pInt;
            uint8_t *pBytes = NULL;
            uint64_t numBytes;

            // Descriptor constructors need a parent atom.  In this
            // case we don't have one so create a dummy one.  This
            // is OK as we only build the descriptor and then dump
            // its contents to a buffer
            MP4Atom     dummyParent(*this);

            // Create the IOD
            MP4Descriptor *pIod = new MP4IODescriptor(dummyParent);
            pIod->SetTag(MP4IODescrTag);
            pIod->Generate();

            // Set audio and video profileLevels
            if (pIod->FindProperty("audioProfileLevelId",
                                   (MP4Property **)&pInt))
                pInt->SetValue(audioProfile);

            if (pIod->FindProperty("visualProfileLevelId",
                                   (MP4Property **)&pInt))
                pInt->SetValue(videoProfile);

            // Mutate esIds from MP4ESIDIncDescrTag to MP4ESDescrTag
            MP4DescriptorProperty *pEsProperty;
            if (!pIod->FindProperty("esIds", (MP4Property **)&pEsProperty)) return;
            pEsProperty->SetTags(MP4ESDescrTag);

            // Add ES Descriptors

            // Scene
            CreateIsmaSceneCommand(
                (audioProfile != 0xFF),
                (videoProfile != 0xFF),
                &pBytes,
                &numBytes);

            log.hexDump(0, MP4_LOG_VERBOSE1, pBytes, numBytes, "\"%s\": Scene data",
                        GetFilename().c_str() );

            char *sceneCmdBase64 = MP4ToBase64(pBytes, numBytes);

            char *urlBuf =
                (char *)MP4Malloc(strlen(sceneCmdBase64) + 64);
            snprintf(urlBuf, strlen(sceneCmdBase64) + 64,
                     "data:application/mpeg4-bifs-au;base64,%s",
                     sceneCmdBase64);

            log.verbose1f("\"%s\": Scene data URL = \042%s\042", GetFilename().c_str(),
                          urlBuf);

            /* MP4Descriptor* pSceneEsd = */
            CreateESD(
                pEsProperty,
                201,                // esid
                MP4SystemsV2ObjectType,
                MP4SceneDescriptionStreamType,
                numBytes,           // bufferSize
                numBytes * 8,       // bitrate
                BifsV2Config,
                sizeof(BifsV2Config),
                urlBuf);
            MP4Free(urlBuf);
            urlBuf = NULL;

            MP4Free(sceneCmdBase64);
            sceneCmdBase64 = NULL;
            MP4Free(pBytes);
            pBytes = NULL;

            // OD

            // Video
            MP4DescriptorProperty *pVideoEsdProperty =
                new MP4DescriptorProperty(dummyParent);
            pVideoEsdProperty->SetTags(MP4ESDescrTag);

            /* MP4Descriptor* pVideoEsd = */
            CreateESD(
                pVideoEsdProperty,
                20,                 // esid
                MP4_MPEG4_VIDEO_TYPE,
                MP4VisualStreamType,
                videoBitrate / 8,   // bufferSize
                videoBitrate,
                videoConfig,
                videoConfigLength,
                NULL);

            // Audio
            MP4DescriptorProperty *pAudioEsdProperty =
                new MP4DescriptorProperty(dummyParent);
            pAudioEsdProperty->SetTags(MP4ESDescrTag);

            /* MP4Descriptor* pAudioEsd = */
            CreateESD(
                pAudioEsdProperty,
                10,                 // esid
                MP4_MPEG4_AUDIO_TYPE,
                MP4AudioStreamType,
                audioBitrate / 8,   // bufferSize
                audioBitrate,
                audioConfig,
                audioConfigLength,
                NULL);

            CreateIsmaODUpdateCommandForStream(
                pAudioEsdProperty,
                pVideoEsdProperty,
                &pBytes,
                &numBytes);

            // cleanup temporary descriptor properties
            delete pAudioEsdProperty;
            delete pVideoEsdProperty;

            log.hexDump(0, MP4_LOG_VERBOSE1, pBytes, numBytes, "\"%s\": OD data = %" PRIu64 " bytes",
                        GetFilename().c_str(), numBytes);

            char *odCmdBase64 = MP4ToBase64(pBytes, numBytes);

            urlBuf = (char *)MP4Malloc(strlen(odCmdBase64) + 64);
            if (urlBuf != NULL)
            {
                snprintf(urlBuf, strlen(odCmdBase64) + 64,
                         "data:application/mpeg4-od-au;base64,%s",
                         odCmdBase64);

                log.verbose1f("\"%s\": OD data URL = \042%s\042", GetFilename().c_str(), urlBuf);

                /* MP4Descriptor* pOdEsd = */
                CreateESD(
                    pEsProperty,
                    101,
                    MP4SystemsV1ObjectType,
                    MP4ObjectDescriptionStreamType,
                    numBytes,       // bufferSize
                    numBytes * 8,   // bitrate
                    NULL,           // config
                    0,              // configLength
                    urlBuf);

                MP4Free(urlBuf);
                urlBuf = NULL;
            }
            MP4Free(odCmdBase64);
            odCmdBase64 = NULL;
            MP4Free(pBytes);
            pBytes = NULL;

            // finally get the whole thing written to a memory
            pIod->WriteToMemory(*this, ppIodBytes, pIodNumBytes);

            delete pIod;

            log.hexDump(0, MP4_LOG_VERBOSE1, *ppIodBytes, *pIodNumBytes, "\"%s\": IOD data",
                        GetFilename().c_str() );
        }

        void MP4File::CreateESD(
            MP4DescriptorProperty *pEsProperty,
            uint32_t esid,
            uint8_t objectType,
            uint8_t streamType,
            uint32_t bufferSize,
            uint32_t bitrate,
            const uint8_t *pConfig,
            uint32_t configLength,
            char *url)
        {
            MP4IntegerProperty *pInt;
            MP4StringProperty *pString;
            MP4BytesProperty *pBytes;
            MP4BitfieldProperty *pBits;

            MP4Descriptor *pEsd =
                pEsProperty->AddDescriptor(MP4ESDescrTag);
            pEsd->Generate();

            if (pEsd->FindProperty("ESID",
                                   (MP4Property **)&pInt))
                pInt->SetValue(esid);

            if (pEsd->FindProperty("decConfigDescr.objectTypeId",
                                   (MP4Property **)&pInt))
                pInt->SetValue(objectType);

            if (pEsd->FindProperty("decConfigDescr.streamType",
                                   (MP4Property **)&pInt))
                pInt->SetValue(streamType);

            if (pEsd->FindProperty("decConfigDescr.bufferSizeDB",
                                   (MP4Property **)&pInt))
                pInt->SetValue(bufferSize);

            if (pEsd->FindProperty("decConfigDescr.maxBitrate",
                                   (MP4Property **)&pInt))
                pInt->SetValue(bitrate);

            if (pEsd->FindProperty("decConfigDescr.avgBitrate",
                                   (MP4Property **)&pInt))
                pInt->SetValue(bitrate);

            MP4DescriptorProperty *pConfigDescrProperty;
            if (pEsd->FindProperty("decConfigDescr.decSpecificInfo",
                                   (MP4Property **)&pConfigDescrProperty))
            {

                MP4Descriptor *pConfigDescr =
                    pConfigDescrProperty->AddDescriptor(MP4DecSpecificDescrTag);
                pConfigDescr->Generate();

                if (pConfigDescrProperty->FindProperty("decSpecificInfo[0].info",
                                                       (MP4Property **)&pBytes))
                    pBytes->SetValue(pConfig, configLength);
            }

            if (pEsd->FindProperty("slConfigDescr.predefined",
                                   (MP4Property **)&pInt))
                // changed 12/5/02 from plugfest to value 0
                pInt->SetValue(0);

            if (pEsd->FindProperty("slConfig.useAccessUnitEndFlag",
                                   (MP4Property **)&pBits))
                pBits->SetValue(1);

            if (url)
            {
                if (pEsd->FindProperty("URLFlag",
                                       (MP4Property **)&pInt))
                    pInt->SetValue(1);

                if (pEsd->FindProperty("URL",
                                       (MP4Property **)&pString))
                    pString->SetValue(url);
            }

            //return pEsd;
        }

        void MP4File::CreateIsmaODUpdateCommandFromFileForFile(
            MP4TrackId odTrackId,
            MP4TrackId audioTrackId,
            MP4TrackId videoTrackId,
            uint8_t **ppBytes,
            uint64_t *pNumBytes)
        {
            // Descriptor constructors need a parent atom.  In this
            // case we don't have one so create a dummy one.  This
            // is OK as we only build the descriptor and then dump
            // its contents to a buffer
            MP4Atom     dummyParent(*this);
            MP4Descriptor *pCommand = CreateODCommand(dummyParent, MP4ODUpdateODCommandTag);
            pCommand->Generate();

            for (uint8_t i = 0; i < 2; i++)
            {
                MP4TrackId trackId;
                uint16_t odId;

                if (i == 0)
                {
                    trackId = audioTrackId;
                    odId = 10;
                }
                else
                {
                    trackId = videoTrackId;
                    odId = 20;
                }

                if (trackId == MP4_INVALID_TRACK_ID)
                {
                    continue;
                }

                MP4DescriptorProperty *pOdDescrProperty =
                    (MP4DescriptorProperty *)(pCommand->GetProperty(0));

                pOdDescrProperty->SetTags(MP4FileODescrTag);

                MP4Descriptor *pOd =
                    pOdDescrProperty->AddDescriptor(MP4FileODescrTag);

                pOd->Generate();

                MP4BitfieldProperty *pOdIdProperty = NULL;
                if (pOd->FindProperty("objectDescriptorId",
                                      (MP4Property **)&pOdIdProperty))
                    pOdIdProperty->SetValue(odId);

                MP4DescriptorProperty *pEsIdsDescriptorProperty = NULL;
                ASSERT(pOd->FindProperty("esIds",
                                         (MP4Property **)&pEsIdsDescriptorProperty));
                ASSERT(pEsIdsDescriptorProperty);

                pEsIdsDescriptorProperty->SetTags(MP4ESIDRefDescrTag);

                MP4Descriptor *pRefDescriptor =
                    pEsIdsDescriptorProperty->AddDescriptor(MP4ESIDRefDescrTag);
                pRefDescriptor->Generate();

                MP4Integer16Property *pRefIndexProperty = NULL;
                ASSERT(pRefDescriptor->FindProperty("refIndex",
                                                    (MP4Property **)&pRefIndexProperty));
                ASSERT(pRefIndexProperty);

                uint32_t mpodIndex = FindTrackReference(
                                         MakeTrackName(odTrackId, "tref.mpod"), trackId);
                ASSERT(mpodIndex != 0);

                pRefIndexProperty->SetValue(mpodIndex);
            }

            pCommand->WriteToMemory(*this, ppBytes, pNumBytes);

            delete pCommand;
        }

        void MP4File::CreateIsmaODUpdateCommandFromFileForStream(
            MP4TrackId audioTrackId,
            MP4TrackId videoTrackId,
            uint8_t **ppBytes,
            uint64_t *pNumBytes)
        {
            MP4DescriptorProperty *pAudioEsd = NULL;
            MP4Integer8Property *pAudioSLConfigPredef = NULL;
            MP4BitfieldProperty *pAudioAccessUnitEndFlag = NULL;
            int oldAudioUnitEndFlagValue = 0;
            MP4DescriptorProperty *pVideoEsd = NULL;
            MP4Integer8Property *pVideoSLConfigPredef = NULL;
            MP4BitfieldProperty *pVideoAccessUnitEndFlag = NULL;
            int oldVideoUnitEndFlagValue = 0;
            MP4IntegerProperty *pAudioEsdId = NULL;
            MP4IntegerProperty *pVideoEsdId = NULL;

            if (audioTrackId != MP4_INVALID_TRACK_ID)
            {
                // changed mp4a to * to handle enca case
                MP4Atom *pEsdsAtom =
                    FindAtom(MakeTrackName(audioTrackId,
                                           "mdia.minf.stbl.stsd.*.esds"));
                ASSERT(pEsdsAtom);

                pAudioEsd = (MP4DescriptorProperty *)(pEsdsAtom->GetProperty(2));
                // ESID is 0 for file, stream needs to be non-ze
                ASSERT(pAudioEsd->FindProperty("ESID",
                                               (MP4Property **)&pAudioEsdId));

                ASSERT(pAudioEsdId);
                pAudioEsdId->SetValue(audioTrackId);

                // SL config needs to change from 2 (file) to 1 (null)
                if (pAudioEsd->FindProperty("slConfigDescr.predefined",
                                            (MP4Property **)&pAudioSLConfigPredef))
                {
                    ASSERT(pAudioSLConfigPredef);
                    pAudioSLConfigPredef->SetValue(0);
                }

                if (pAudioEsd->FindProperty("slConfigDescr.useAccessUnitEndFlag",
                                            (MP4Property **)&pAudioAccessUnitEndFlag))
                {
                    oldAudioUnitEndFlagValue =
                        pAudioAccessUnitEndFlag->GetValue();
                    pAudioAccessUnitEndFlag->SetValue(1);
                }
            }

            if (videoTrackId != MP4_INVALID_TRACK_ID)
            {
                // changed mp4v to * to handle encv case
                MP4Atom *pEsdsAtom =
                    FindAtom(MakeTrackName(videoTrackId,
                                           "mdia.minf.stbl.stsd.*.esds"));
                ASSERT(pEsdsAtom);

                pVideoEsd = (MP4DescriptorProperty *)(pEsdsAtom->GetProperty(2));
                ASSERT(pVideoEsd->FindProperty("ESID",
                                               (MP4Property **)&pVideoEsdId));

                ASSERT(pVideoEsdId);
                pVideoEsdId->SetValue(videoTrackId);

                // SL config needs to change from 2 (file) to 1 (null)
                ASSERT(pVideoEsd->FindProperty("slConfigDescr.predefined",
                                               (MP4Property **)&pVideoSLConfigPredef));
                ASSERT(pVideoSLConfigPredef);
                pVideoSLConfigPredef->SetValue(0);

                if (pVideoEsd->FindProperty("slConfigDescr.useAccessUnitEndFlag",
                                            (MP4Property **)&pVideoAccessUnitEndFlag))
                {
                    oldVideoUnitEndFlagValue =
                        pVideoAccessUnitEndFlag->GetValue();
                    pVideoAccessUnitEndFlag->SetValue(1);
                }
            }

            CreateIsmaODUpdateCommandForStream(
                pAudioEsd, pVideoEsd, ppBytes, pNumBytes);
            log.hexDump(0, MP4_LOG_VERBOSE1, *ppBytes, *pNumBytes,
                        "\"%s\": After CreateImsaODUpdateCommandForStream len %" PRIu64,
                        GetFilename().c_str(), *pNumBytes);

            // return SL config values to 2 (file)
            // return ESID values to 0
            if (pAudioSLConfigPredef)
            {
                pAudioSLConfigPredef->SetValue(2);
            }
            if (pAudioEsdId)
            {
                pAudioEsdId->SetValue(0);
            }
            if (pAudioAccessUnitEndFlag)
            {
                pAudioAccessUnitEndFlag->SetValue(oldAudioUnitEndFlagValue );
            }
            if (pVideoEsdId)
            {
                pVideoEsdId->SetValue(0);
            }
            if (pVideoSLConfigPredef)
            {
                pVideoSLConfigPredef->SetValue(2);
            }
            if (pVideoAccessUnitEndFlag)
            {
                pVideoAccessUnitEndFlag->SetValue(oldVideoUnitEndFlagValue );
            }
        }

        void MP4File::CreateIsmaODUpdateCommandForStream(
            MP4DescriptorProperty *pAudioEsdProperty,
            MP4DescriptorProperty *pVideoEsdProperty,
            uint8_t **ppBytes,
            uint64_t *pNumBytes)
        {
            MP4Descriptor *pAudioOd = NULL;
            MP4Descriptor *pVideoOd = NULL;

            // Descriptor constructors need a parent atom.  In this
            // case we don't have one so create a dummy one.  This
            // is OK as we only build the descriptor and then dump
            // its contents to a buffer
            MP4Atom     dummyParent(*this);
            MP4Descriptor *pCommand =
                CreateODCommand(dummyParent, MP4ODUpdateODCommandTag);
            pCommand->Generate();

            for (uint8_t i = 0; i < 2; i++)
            {
                uint16_t odId;
                MP4DescriptorProperty *pEsdProperty = NULL;

                if (i == 0)
                {
                    odId = 10;
                    pEsdProperty = pAudioEsdProperty;
                }
                else
                {
                    odId = 20;
                    pEsdProperty = pVideoEsdProperty;
                }

                if (pEsdProperty == NULL)
                {
                    continue;
                }

                MP4DescriptorProperty *pOdDescrProperty =
                    (MP4DescriptorProperty *)(pCommand->GetProperty(0));

                pOdDescrProperty->SetTags(MP4ODescrTag);

                MP4Descriptor *pOd =
                    pOdDescrProperty->AddDescriptor(MP4ODescrTag);
                pOd->Generate();

                if (i == 0)
                {
                    pAudioOd = pOd;
                }
                else
                {
                    pVideoOd = pOd;
                }

                MP4BitfieldProperty *pOdIdProperty = NULL;
                if (pOd->FindProperty("objectDescriptorId",
                                      (MP4Property **)&pOdIdProperty))
                {
                    pOdIdProperty->SetValue(odId);
                }

                delete (MP4DescriptorProperty *)pOd->GetProperty(4);
                pOd->SetProperty(4, pEsdProperty);
            }

            // serialize OD command
            pCommand->WriteToMemory(*this, ppBytes, pNumBytes);

            // detach from esd descriptor params
            if (pAudioOd)
            {
                pAudioOd->SetProperty(4, NULL);
            }
            if (pVideoOd)
            {
                pVideoOd->SetProperty(4, NULL);
            }

            // then destroy
            delete pCommand;
        }

        void MP4File::CreateIsmaSceneCommand(
            bool hasAudio,
            bool hasVideo,
            uint8_t **ppBytes,
            uint64_t *pNumBytes)
        {
            // from ISMA 1.0 Tech Spec Appendix E
            static const uint8_t bifsAudioOnly[] =
            {
                0xC0, 0x10, 0x12,
                0x81, 0x30, 0x2A, 0x05, 0x6D, 0xC0
            };
            static const uint8_t bifsVideoOnly[] =
            {
                0xC0, 0x10, 0x12,
                0x61, 0x04,
                0x1F, 0xC0, 0x00, 0x00,
                0x1F, 0xC0, 0x00, 0x00,
                0x44, 0x28, 0x22, 0x82, 0x9F, 0x80
            };
            static const uint8_t bifsAudioVideo[] =
            {
                0xC0, 0x10, 0x12,
                0x81, 0x30, 0x2A, 0x05, 0x6D, 0x26,
                0x10, 0x41, 0xFC, 0x00, 0x00, 0x01, 0xFC, 0x00, 0x00,
                0x04, 0x42, 0x82, 0x28, 0x29, 0xF8
            };

            if (hasAudio && hasVideo)
            {
                *pNumBytes = sizeof(bifsAudioVideo);
                *ppBytes = (uint8_t *)MP4Malloc(*pNumBytes);
                memcpy(*ppBytes, bifsAudioVideo, sizeof(bifsAudioVideo));

            }
            else if (hasAudio)
            {
                *pNumBytes = sizeof(bifsAudioOnly);
                *ppBytes = (uint8_t *)MP4Malloc(*pNumBytes);
                memcpy(*ppBytes, bifsAudioOnly, sizeof(bifsAudioOnly));

            }
            else if (hasVideo)
            {
                *pNumBytes = sizeof(bifsVideoOnly);
                *ppBytes = (uint8_t *)MP4Malloc(*pNumBytes);
                memcpy(*ppBytes, bifsVideoOnly, sizeof(bifsVideoOnly));
            }
            else
            {
                *pNumBytes = 0;
                *ppBytes = NULL;
            }
        }

        ///////////////////////////////////////////////////////////////////////////////

    }
} // namespace mp4v2::impl
